#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdarg.h>
#include <arpa/inet.h>
#include <sys/mman.h>
#include "parse_url.h"
#include "tinyserver.h"
#include "do_server.h"

static void handle_error (char *msg)
{
	perror(msg);
	exit(EXIT_FAILURE);
}

static void usage (void) 
{
	printf( "Usage:\n"
			"\t-p 	Port to listen\n"
			"\t-r	Server root path\n");

	exit(EXIT_FAILURE);
}

void parse_args (int argc, char **argv, Setting *s)
{
	int opt;

	if (argc == 1) {
		usage();
	}

	while ((opt = getopt(argc, argv, "p:r:")) != -1) {
		switch(opt) {
			case 'p':
				s->st_port = atoi(optarg);
				if (s->st_port <= 0) {
					usage();
				}
				break;
			case 'r':
				strncpy(s->st_root, optarg, 1024);
				break;
			default:
				usage();
				break;
		}
	}
}

int open_listen_fd (Setting *s) {
	int listenfd;
	struct sockaddr_in servaddr;
	int yes = 1;

	if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		return -1;

	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)))
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(s->st_port);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr)) == -1)
		return -1;

	if (listen(listenfd, LISTENQ) == -1)
		return -1;

	return listenfd;

}

int accept_connection (int listenfd) 
{
	struct sockaddr_in cliaddr;
	socklen_t addrlen;
	int servfd;
	char addrbuf[INET_ADDRSTRLEN];

	addrlen = sizeof(cliaddr);

	if ((servfd = accept(listenfd, (struct sockaddr*)&cliaddr, &addrlen)) == -1)
		return -1;

	if (inet_ntop(AF_INET, &(cliaddr.sin_addr), addrbuf, INET_ADDRSTRLEN) == NULL)
		handle_error("net_inet failed");

	/* debug */
	printf("Get connection from %s\n", addrbuf);

	return servfd;
}

void dispatch (int fd, Setting *setting)
{
	TinyBuff tb;
	Url url;
	char line[1024];

	tinybuf_init(&tb, fd);

	/*while (strncmp(read_line_from_buf(&tb, line), "\r\n", MAXLINE)) {
		printf("line read: %s", line);
	}*/
	parse_url(read_line_from_buf(&tb, line), &url, setting);

	switch (url.method) {
		case HTTP_POST:
			do_post(&tb, &url);
			break;
		case HTTP_GET:
			do_get(&tb, &url);
			break;
		default:
			break;
	}
}

void sig_handler (int sig)
{
	pid_t wpid;
	while ((wpid = waitpid(-1, NULL, WNOHANG)) > 0) {
		printf("child process %d died\n", wpid);
	}
}	

int main (int argc, char *argv[])
{
	int listenfd, servfd;
	Setting setting;
	pid_t pid;

	struct sigaction sa;
	sa.sa_handler = sig_handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	if (sigaction(SIGCHLD, &sa, NULL) == -1)
		handle_error("sig action failed");

	parse_args(argc, argv, &setting);
#ifndef DEBUG
	printf("Port: %d\n", setting.st_port);
	printf("Root: %s\n", setting.st_root);
#endif
	if ((listenfd = open_listen_fd(&setting)) == -1) {
		handle_error("open listen fd failed");
	}

	while (1) {
		if ((servfd = accept_connection(listenfd)) == -1) {
			handle_error("accept failed");
		}

		if ((pid = fork()) == 0) {
			close(listenfd);
			dispatch(servfd, &setting);
			return 0;
		} else if (pid > 0) {
			close(servfd);
		} else {
			handle_error("fork faild");
		}
	}
	return 0;
}
